import { clamp01, Vec3, Vec4 } from "cc";

export class Spline {

    /**
     * Compute the tangent of a 4-point 1-dimensional bezier spline
     * @param out 
     * @param t  How far along the spline (0...1)
     * @param p0 First point
     * @param p1 First tangent
     * @param p2 Second point
     * @param p3 Second tangent
     * @returns 
     */
    public static bezier3 (out: Vec3, t: number, p0: Vec3, p1: Vec3, p2: Vec3, p3: Vec3) {
        t = clamp01(t);
        let d = 1 - t;
        let k0 = d * d * d;
        let k1 = 3 * d * d * t;
        let k2 = 3 * d * t * t;
        let k3 = t * t * t;

        return out.set(
            k0 * p0.x + k1 * p1.x + k2 * p2.x + k3 * p3.x,
            k0 * p0.y + k1 * p1.y + k2 * p2.y + k3 * p3.y,
            k0 * p0.z + k1 * p1.z + k2 * p2.z + k3 * p3.z,
        )
    }

    // Use the Thomas algorithm to compute smooth tangent values for a spline.  
    // Resultant tangents guarantee second-order smoothness of the curve.
    public static computeSmoothControlPoints (knot: Vec4[], ctrl1: Vec4[], ctrl2: Vec4[]) {
        let numPoints = knot.length;
        if (numPoints <= 2) {
            if (numPoints == 2) {
                Vec4.lerp(ctrl1[0], knot[0], knot[1], 0.33333);
                Vec4.lerp(ctrl2[0], knot[0], knot[1], 0.66666);
            }
            else if (numPoints == 1) {
                ctrl1[0] = ctrl2[0] = knot[0];
            }
            return;
        }

        let a = new Array(numPoints);
        let b = new Array(numPoints);
        let c = new Array(numPoints);
        let r = new Array(numPoints);
        let knot_any: any = knot;
        let ctrl1_any: any = ctrl1;
        let ctrl2_any: any = ctrl2;
        let axis_list = ["x", "y", "z", "w"];

        for (let k = 0; k < 4; ++k) {
            let axis = axis_list[k];
            let n = numPoints - 1;

            // Linear into the first segment
            a[0] = 0;
            b[0] = 2;
            c[0] = 1;
            r[0] = knot_any[0][axis] + 2 * knot_any[1][axis];

            // Internal segments
            for (let i = 1; i < n - 1; ++i) {
                a[i] = 1;
                b[i] = 4;
                c[i] = 1;
                r[i] = 4 * knot_any[i][axis] + 2 * knot_any[i + 1][axis];
            }

            // Linear out of the last segment
            a[n - 1] = 2;
            b[n - 1] = 7;
            c[n - 1] = 0;
            r[n - 1] = 8 * knot_any[n - 1][axis] + knot_any[n][axis];

            // Solve with Thomas algorithm
            for (let i = 1; i < n; ++i) {
                let m = a[i] / b[i - 1];
                b[i] = b[i] - m * c[i - 1];
                r[i] = r[i] - m * r[i - 1];
            }

            // Compute ctrl1
            ctrl1_any[n - 1][axis] = r[n - 1] / b[n - 1];
            for (let i = n - 2; i >= 0; --i) {
                ctrl1_any[i][axis] = (r[i] - c[i] * ctrl1_any[i + 1][axis]) / b[i];
            }

            // Compute ctrl2 from ctrl1
            for (let i = 0; i < n; i++) {
                ctrl2_any[i][axis] = 2 * knot_any[i + 1][axis] - ctrl1_any[i + 1][axis];
            }

            ctrl2_any[n - 1][axis] = 0.5 * (knot_any[n][axis] + ctrl1_any[n - 1][axis]);
        }
    }


    public static computeSmoothControlPointsLooped (knot: Vec4[], ctrl1: Vec4[], ctrl2: Vec4[]) {
        let numPoints = knot.length;
        if (numPoints < 2) {
            if (numPoints == 1)
                ctrl1[0] = ctrl2[0] = knot[0];
            return;
        }

        let margin = Math.min(4, numPoints - 1);
        let length = numPoints + 2 * margin;
        let knotLooped: Vec4[] = new Array<Vec4>(length);
        let ctrl1Looped: Vec4[] = new Array<Vec4>(length);
        let ctrl2Looped: Vec4[] = new Array<Vec4>(length);

        for (let i = 0; i < margin; ++i) {
            knotLooped[i] = knot[numPoints - (margin - i)];
            knotLooped[numPoints + margin + i] = knot[i];
        }

        for (let i = 0; i < numPoints; ++i) {
            knotLooped[i + margin] = knot[i];
        }

        for (let i = 0; i < length; ++i) {
            ctrl1Looped[i] = new Vec4();
            ctrl2Looped[i] = new Vec4();
        }

        Spline.computeSmoothControlPoints(knotLooped, ctrl1Looped, ctrl2Looped);

        for (let i = 0; i < numPoints; ++i) {
            ctrl1[i] = ctrl1Looped[i + margin];
            ctrl2[i] = ctrl2Looped[i + margin];
        }
    }
}